package ezXpns;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * 
 * @author A0086934R
 * 
 * Superclass for both Expense and Income. Stores common
 * variables and methods for Expense and Income.
 */

//@author A0086934R
public class BudgetChange {

	/**
	 * Gets the amount 
	 * @return amount
	 */
	public float getAmount() {
		return amount;
	}
	
	/**
	 * Sets the amount
	 * @param amount input amount
	 */
	public void setAmount(float amount) {
		this.amount = amount;
	}
	
	/**
	 * Gets the name
	 * @return name
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Sets the name
	 * @param name input name
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Returns an Iterable of all the tags
	 * @return Iterable<Tag> tags
	 */
	public Iterable<Tag> getAllTags() {
		return tags;
	}
	
	/**
	 * Gets a particular tag
	 * @param name name of tag to search
	 * @return first Tag that matchs the input name. Null if no match found
	 */
	public Tag getTag(String name) {
		for(Tag tag: tags) {
			if(tag.getName().equals(name))
				return tag;
		}
		
		return null;
	}
	
	/**
	 * Checks if object contains a specific Tag
	 * @param name name of Tag to search
	 * @return true if contains Tag, false if does not contain Tag
	 */
	public boolean hasTag(String name) {
		return getTag(name) != null;
	}
	
	/**
	 * Removes a specific Tag
	 * @param name name of Tag to remove
	 */
	public void removeTag(String name) {
		Iterator<Tag> itr = tags.iterator();
		while(itr.hasNext()) {
			Tag tag = itr.next();
			if(tag.getName().equals(name)) {
				itr.remove();
				return;
			}
		}
	}
	
	/**
	 * Adds a Tag
	 * @param tag Tag to be added
	 */
	public void addTag(Tag tag) {
		removeTag(tag.getName());
		tags.add(tag);
	}
	
	/**
	 * Returns a string of all the Tags
	 * @return String of names of all the Tags with 
	 * a space in between each Tag
	 */
	public String getTagsAsString() {
		StringBuilder str = new StringBuilder();
		boolean first = true;
		
		for(Tag tag: tags) {
			if(!first) {
				str.append(" ");
			}
			else {
				first = false;
			}
			str.append(tag.toString());
		}
		
		return str.toString();
	}
	
	/**
	 * Sets Tags from an input string
	 * @param str a String of names of Tags separated by " "
	 */

	//@author A0088827L
	public void setTagsFromString(String str) {
		tags.clear();
		
		//Lex
		//Split using space and then colon
		ArrayList<String> tokens = new ArrayList<String>(); 
		for(String token: str.split(" ")) {
			String[] subtokens = token.split(":");
			boolean first = true;
			
			for(String subtoken: subtokens) {
				if(!first) {
					tokens.add(":");
				}
				else {
					first = false;
				}
				
				if(subtoken.length() > 0)//a bunch of colon will become ""
					tokens.add(subtoken);
			}
			
			//take care of trailing colons
			if(token.endsWith(":"))
				tokens.add(":");
		}
		
		//Parse
		String tagNameCandidate = null;//a one level deep "stack" to store the previous non-colon token
		boolean createTagWithValue = false;	
		
		for(String token: tokens) {
			if(token.equals(":")) {
				//if there's a token in the stack
				if(tagNameCandidate != null) {
					createTagWithValue = true;//the next token should be used as the tag value
				}
			}
			else {
				if(createTagWithValue) {//Reached the value of a tag
					addTag(new Tag(tagNameCandidate, token));
					createTagWithValue = false;
					tagNameCandidate = null;
				}
				else {
					if(tagNameCandidate == null) {//If the stack is empty
						tagNameCandidate = token;//push
					}
					else {//if it is filled, pop both
						addTag(new Tag(tagNameCandidate));
						addTag(new Tag(token));
						tagNameCandidate = null;
					}
				}
			}
		}
		
		//clean the stack
		if(tagNameCandidate != null) addTag(new Tag(tagNameCandidate));
	}
	
	/**
	 * Gets the date 
	 * @return date
	 */

	//@author A0086934R
	public Date getDate() {
		return date;
	}
	
	/**
	 * Sets the date
	 * @param date input date
	 */
	public void setDate(Date date) {
		this.date.setTime(date.getTime());
	}

	/**
	 * Compares this object to the specified object.
	 * 
	 * @return true or false. True if the object is equivalent to the specified object, false if 
	 * otherwise.
	 */
	@Override
	public boolean equals(Object other) {
		if(other == this) return true;
		if(other == null) return false;
		if(!(other instanceof BudgetChange)) return false;
		
		BudgetChange otherChange = (BudgetChange) other;
		
		if(!otherChange.type.equals(type)) return false;
		if(!otherChange.date.equals(date)) return false;
		if(Math.abs(otherChange.amount - amount) > 0.0001f) return false;
		if(!otherChange.name.equals(name)) return false;
		
		if(otherChange.tags.size() != tags.size()) return false;
		
		for(Tag tag: otherChange.tags) {
			Tag myTag = getTag(tag.getName());
			if(myTag == null || !myTag.equals(tag)) return false;
		}
		
		return true;
	}
	
	/**
	 * Gets the payment Type
	 * @return paymentType
	 */
	public String getType() {
		return type;
	}
	/**
	 * Sets the payment type
	 * @param type the payment Type
	 */
	public void setType(String type) {
		this.type = type;
	}
	
	/**
	 * Creates a dulipate of the object and returns the duplicated copy
	 * 
	 * @return clone 
	 */
	public BudgetChange clone() {
		BudgetChange clone = new BudgetChange();
		clone.type = type;
		clone.date.setTime(date.getTime());
		clone.amount = amount;
		clone.name = name;
		clone.tags.addAll(tags);
		
		return clone;
	}
	
	private String type;
	private Date date = new Date();
	private float amount;
	private String name;
	private List<Tag> tags = new ArrayList<Tag>();
}